package com.season.summer.bspgenerator.utils;

import com.season.summer.bspgenerator.entity.ColumnEntity;
import com.season.summer.bspgenerator.entity.PackageInfo;
import com.season.summer.bspgenerator.entity.TableEntity;
import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.NullCacheStorage;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


/**
 * @author wqy214
 */
public class GeneratorUtil {

    public static String packagePath;

    public static String projectPath;

    private static String vuePath;

    public static String tablePrefix;

    private static PropertiesConfiguration config;

    private static String SEPARATOR =  "\\";

    private static String[] templates = new String[]{"Entity.java.ftl", "VO.java.ftl", "Mapper.xml.ftl", "Mapper.java.ftl", "Service.java.ftl", "ServiceImpl.java.ftl", "Controller.java.ftl", ".vue.ftl"};

    private static final Configuration FREEMARKER_CONFIGURATION = new Configuration(Configuration.VERSION_2_3_28);

    static {
        FREEMARKER_CONFIGURATION.setTemplateLoader(new ClassTemplateLoader(GeneratorUtil.class, "/templates"));
        FREEMARKER_CONFIGURATION.setDefaultEncoding("UTF-8");
        FREEMARKER_CONFIGURATION.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        FREEMARKER_CONFIGURATION.setCacheStorage(NullCacheStorage.INSTANCE);
        try {
            config = new PropertiesConfiguration("generator.properties");
            packagePath = config.getString("package-path");
            vuePath = config.getString("vue-path");
            projectPath = config.getString("project-path");
            tablePrefix = config.getString("table_prefix");
        } catch (ConfigurationException e) {
            System.out.println("读取代码生成配置文件失败");
        }
    }

    public static void generatorCode(PackageInfo packageInfo, List<Map<String, String>> columns, ZipOutputStream zip) {
        boolean hasBigDecimal = false;
        boolean hasDate = false;
        //表信息
        TableEntity tableEntity = new TableEntity();
        //表名转换成Java类名
        String className = packageInfo.getClassName();
        tableEntity.setClassName(className);
        String classname = StringUtils.uncapitalize(className);
        tableEntity.setClassname(classname);
        packageInfo.setClassname(classname);

        //列信息
        List<ColumnEntity> columsList = new ArrayList<>();
        List<ColumnEntity> keyColumList = new ArrayList<>();
        for (Map<String, String> column : columns) {
            ColumnEntity columnEntity = new ColumnEntity();
            columnEntity.setColumnName(column.get("columnName"));
            columnEntity.setDataType(column.get("dataType"));
            columnEntity.setComments(column.get("columnComment"));
            columnEntity.setExtra(column.get("extra"));

            //列名转换成Java属性名
            String attrName = columnToJava(columnEntity.getColumnName());
            columnEntity.setAttrName(attrName);
            columnEntity.setAttrname(StringUtils.uncapitalize(attrName));

            //列的数据类型，转换成Java类型
            String attrType = config.getString(columnEntity.getDataType(), "unknowType");
            columnEntity.setAttrType(attrType);
            if (!hasBigDecimal && "BigDecimal".equals(attrType)) {
                hasBigDecimal = true;
            }
            if (!hasDate && "Date".equals(attrType)) {
                hasDate = true;
            }
            //是否主键
            String columnKey = column.get("columnKey");
            if ("PRI".equalsIgnoreCase(columnKey) && tableEntity.getPk() == null) {
                tableEntity.setPk(columnEntity);
                continue;
            }

            if ("MUL".equalsIgnoreCase(columnKey) || "UNI".equalsIgnoreCase(columnKey)) {
                keyColumList.add(columnEntity);
            }

            columsList.add(columnEntity);
        }
        tableEntity.setKeys(keyColumList);
        tableEntity.setColumns(columsList);

        //没主键，则第一个字段为主键
        if (tableEntity.getPk() == null) {
            tableEntity.setPk(tableEntity.getColumns().get(0));
        }

        Map<String, Object> data = new HashMap<>();
        String moduleName = packageInfo.getModuleName();
        data.put("tableName", packageInfo.getTableName());
        data.put("pk", tableEntity.getPk());
        data.put("keys", tableEntity.getKeys());
        data.put("className", tableEntity.getClassName());
        data.put("classname", tableEntity.getClassname());
        data.put("pathName", tableEntity.getClassname().toLowerCase());
        data.put("packagePath", packagePath);
        data.put("columns", tableEntity.getColumns());
        data.put("hasBigDecimal", hasBigDecimal);
        data.put("hasDate", hasDate);
        data.put("entityPackage", packagePath + "." + moduleName + ".entity");
        data.put("voPackage", packagePath + "." + moduleName + ".vo");
        data.put("controllerPackage", packagePath + "." + moduleName + ".controller");
        data.put("servicePackage", packagePath + "." + moduleName + ".service");
        data.put("daoPackage", packagePath + "." + moduleName + ".mapper");

        for (String template : templates) {
            if (zip != null) {
                try {
                    StringWriter sw = new StringWriter();
                    Template tpm = FREEMARKER_CONFIGURATION.getTemplate(template);
                    tpm.process(data, sw);
                    zip.putNextEntry(new ZipEntry(getFileName(template, tableEntity.getClassName())));
                    IOUtils.write(sw.toString(), zip, "UTF-8" );
                    IOUtils.closeQuietly(sw);
                    zip.closeEntry();
                } catch (Exception e) {
                    throw new RuntimeException("渲染模板失败，表名：" + packageInfo.getTableName(), e);
                }
            } else {
                File file = getFile(template, packageInfo);
                if (file.exists()) {
                    GeneratorUtil.renderTemplate(template, file, data);
                }
            }
        }
    }

    /**
     * 获取文件名
     */
    private static String getFileName(String template, String className) {
        String fileName;
        switch(template){
            case "Entity.java.ftl":
                fileName = className + ".java";
                break;
            case "VO.java.ftl":
                fileName = className + "VO.java";
                break;
            case "Mapper.xml.ftl":
                fileName = className + "Mapper.xml";
                break;
            case "Mapper.java.ftl":
                fileName = className + "Mapper.java";
                break;
            case "Service.java.ftl":
                fileName = className + "Service.java";
                break;
            case "ServiceImpl.java.ftl":
                fileName = className + "ServiceImpl.java";
                break;
            case "Controller.java.ftl":
                fileName = className + "Controller.java";
                break;
            case ".vue.ftl":
                fileName = className + ".vue";
                break;
            default : //可选
                fileName = "undefined";
        }
        return fileName;

    }

    private static void renderTemplate(final String templateName, File file, Map<String,Object> data) {

        try (Writer writer  = new OutputStreamWriter(new FileOutputStream(file, false), StandardCharsets.UTF_8)) {
            Template template = FREEMARKER_CONFIGURATION.getTemplate(templateName);
            template.process(data, writer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 列名转换成Java属性名
     */
    public static String columnToJava(String columnName) {
        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
    }


    /**
     * 获取文件名
     */
    private static File getFile(String template, PackageInfo packageInfo) {

        String path;
        StringBuilder modulePath = getModulePath(packageInfo.getModuleName());
        String suffix = template.replace("template/", "").replace(".ftl", "");
        String fileName;
        if ("Entity.java".equals(suffix)) {
            fileName = packageInfo.getClassName() + ".java";
        } else {
            fileName = packageInfo.getClassName() + suffix;
        }
        if (fileName.endsWith(".vue")) {
            path = getVuePath().append("components\\").append(packageInfo.getClassname()).append(SEPARATOR)
                    .append(packageInfo.getClassname()).append(".vue").toString();
        } else if (fileName.endsWith("Mapper.xml") || fileName.endsWith("Mapper.java.ftl")) {
            path = modulePath.append("mapper\\").append(fileName).toString();
        } else if (fileName.endsWith("VO.java")) {
            path = modulePath.append("vo\\").append(fileName).toString();
        } else if (fileName.endsWith("ServiceImpl.java")) {
            path = modulePath.append("service\\impl\\").append(fileName).toString();
        } else {
            path = modulePath.append(suffix.split("\\.")[0].toLowerCase()).append(SEPARATOR).append(fileName).toString();
        }


        File file = new File(path);
        File parentFile = file.getParentFile();
        if (!file.exists()) {
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return file;
    }

    private static StringBuilder getModulePath(String moduleName) {

        StringBuilder path = new StringBuilder(projectPath);
        if (!projectPath.endsWith(SEPARATOR)) {
            path.append(SEPARATOR);
        }
        path.append("src\\main\\java\\");
        String[] packagePath = GeneratorUtil.packagePath.split("\\.");
        for (String p : packagePath) {
            path.append(p);
            path.append(SEPARATOR);
        }
        path.append(moduleName);
        path.append(SEPARATOR);
        return path;
    }

    private static StringBuilder getVuePath() {
        StringBuilder path = new StringBuilder(vuePath);
        if (!projectPath.endsWith(SEPARATOR)) {
            path.append(SEPARATOR);
        }
        path.append("src\\");
        return path;
    }
}